#include <memory>
#include <mio.h>
#include <sstream>
#include "SRGLinc.h"
#include "SRShader.h"
#include "SRGLMachine.h"

#if _GLES2_

using std::vector;
using std::string;

namespace sora
{
	Shader::Shader(GLint type, const char *src)
	: type_(type), handle_(-1)
	{
		std::string srcStr(src);
		load(srcStr);
	}
	Shader::Shader(GLint type, const std::string &src)
	: type_(type), handle_(-1)
	{
		load(src);
	}
	void Shader::load(const std::string &src)
	{
		handle_ = glCreateShader(type());
		const char *srcPtr = src.c_str();
		glShaderSource(handle_, 1, &srcPtr, 0);
		glCompileShader(handle_);

		GLint status;
		glGetShaderiv(handle_, GL_COMPILE_STATUS, &status);
		if(status == GL_FALSE)
		{
			/*
			char msg[256];
			glGetShaderInfoLog(handle_, sizeof(msg), 0, &msg[0]);
			mio::Console::console().print(msg);
			SR_ASSERT(false);
			*/
			GLint infoLen = 0;
			glGetShaderiv(handle_, GL_INFO_LOG_LENGTH, &infoLen);
			if (infoLen) {
				char* buf = (char*) malloc(infoLen);
				if (buf) {
					glGetShaderInfoLog(handle_, infoLen, NULL, buf);
					LOGE("Could not compile shader %d:\n%s\n", type(), buf);
					free(buf);
				}
				glDeleteShader(handle_);
				handle_ = -1;
			}
		}

		SR_ASSERT(handle() != -1);
	}
	Shader::~Shader()
	{
		if(handle_ != -1)
			glDeleteShader(handle_);
		handle_ = -1;
	}
	Shader *Shader::createVertShader(const std::string &src)
	{
		return new Shader(GL_VERTEX_SHADER, src);
	}
	Shader *Shader::createFragShader(const std::string &src)
	{
		return new Shader(GL_FRAGMENT_SHADER, src);
	}
	GLuint Shader::handle() const { return handle_; }
	GLint Shader::type() const { return type_; }

	ShaderProgram::ShaderProgram(Shader *vertShader, Shader *fragShader)
	: program_(-1), vShader_(vertShader), fShader_(fragShader)
	{
		//인자로 넘어온 쉐이더가 널이면 좃망
		assert(vShader_.get() != NULL);
		assert(fShader_.get() != NULL);

		init();
	}
	ShaderProgram::ShaderProgram(const std::string &vertSrc, const std::string &fragSrc)
	: program_(-1), vShader_(Shader::createVertShader(vertSrc)), fShader_(Shader::createFragShader(fragSrc))
	{
		init();
	}

	bool ShaderProgram::init()
	{
		//create shader program
		program_ = glCreateProgram();

		//attach shader to program
		glAttachShader(program_, vShader_->handle());
		sora::gl::checkGlError("glAttachShader");
		glAttachShader(program_, fShader_->handle());
		sora::gl::checkGlError("glAttachShader");

		//최에 vert/frag가 입력되면 자동 link. 이후에는 수동으로 link를 해준다
		// Link program.
		if(link() == false)
		{
			return false;
		}
		return true;
	}

	bool ShaderProgram::setShader(Shader *vertShader, Shader *fragShader)
	{
		assert(vertShader != NULL && fragShader != NULL);
		glDetachShader(program_, vShader_->handle());
		glDetachShader(program_, fShader_->handle());

		vShader_ = std::auto_ptr<Shader>(vertShader);
		fShader_ = std::auto_ptr<Shader>(fragShader);

		glAttachShader(program_, vShader_->handle());
		sora::gl::checkGlError("glAttachShader");
		glAttachShader(program_, fShader_->handle());
		sora::gl::checkGlError("glAttachShader");

		return link();
	}

	ShaderProgram::~ShaderProgram()
	{
		if(program_ == -1)
			glDeleteProgram(program_);
		program_ = -1;
	}
	void ShaderProgram::setVertShader(Shader *vertShader)
	{
		assert(vertShader != NULL);
		glDetachShader(program_, vShader_->handle());
		vShader_ = std::auto_ptr<Shader>(vertShader);
		glAttachShader(program_, vShader_->handle());
		sora::gl::checkGlError("glAttachShader");
	}
	void ShaderProgram::setFragShader(Shader *fragShader)
	{
		assert(fragShader != NULL);
		glDetachShader(program_, fShader_->handle());
		fShader_ = std::auto_ptr<Shader>(fragShader);
		glAttachShader(program_, fShader_->handle());
		sora::gl::checkGlError("glAttachShader");
	}
	void ShaderProgram::use() const 
	{
		glUseProgram(program_); 
		sora::gl::checkGlError("glUseProgram");
	}
	GLuint ShaderProgram::handle() const { return program_; }
	bool ShaderProgram::validate()
	{
		GLint logLength, status;

		glValidateProgram(program_);
		glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &logLength);
		if (logLength > 0)
		{
			char *log = (char *)malloc(logLength);
			glGetProgramInfoLog(program_, logLength, &logLength, log);

			LOGE("Program validate log: %s", log);
			free(log);
		}

		glGetProgramiv(program_, GL_VALIDATE_STATUS, &status);
		if (status == 0)
			return false;

		return true;
	}
	bool ShaderProgram::link()
	{
		//link program
		glLinkProgram(program_);

		GLint status;
		glGetProgramiv(program_, GL_LINK_STATUS, &status);
		if(status == GL_FALSE)
		{
			/*
			char msg[256];
			glGetProgramInfoLog(program_, sizeof(msg), 0, &msg[0]);
			mio::Console::console().print(msg);
			return false;
			*/
			GLint bufLength = 0;
			glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &bufLength);
			if (bufLength) {
				char* buf = (char*) malloc(bufLength);
				if (buf) {
					glGetProgramInfoLog(program_, bufLength, NULL, buf);
					LOGE("Could not link program:\n%s\n", buf);
					free(buf);
				}
			}
			glDeleteProgram(program_);
			program_ = 0;
			return false;
		}
		return true;
	}
	/*
	void ShaderProgram::applyCamera(const Camera &cam) const
	{
		GLint projectionUniform = getUniformLocation(SHADER_PROJECTION);
		glUniformMatrix4fv(projectionUniform, 1, 0, cam.pointer());
	}

	void ShaderProgram::applyModelview(const matsu::mat4 &m) const
	{
		GLint modelviewUniform = getUniformLocation(SHADER_MODELVIEW);
		glUniformMatrix4fv(modelviewUniform, 1, 0, m.pointer());
	}

	void ShaderProgram::applyVertex2AttribArray(const Vertex2 *vertices, int vertexCount, GLenum drawMode) const
	{
		//buffer사용을 이전에 했을지도 모르면 놓기
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		GLuint positionSlot = getAttribLocation(SHADER_POSITION);
		GLuint colorSlot = getAttribLocation(SHADER_SOURCE_COLOR);

		glEnableVertexAttribArray(positionSlot);
		glEnableVertexAttribArray(colorSlot);

		GLsizei stride = sizeof(Vertex2);
		const GLvoid *pCoords = &vertices[0].pos[0];
		const GLvoid *pColors = &vertices[0].color[0];

		glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
		glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);

		glDrawArrays(drawMode, 0, vertexCount);

		glDisableVertexAttribArray(positionSlot);
		glDisableVertexAttribArray(colorSlot);
	}

	void ShaderProgram::applyVertex2Index(const Vertex2 *vertices, const GLvoid *indices, int indexCount, GLenum drawMode, GLenum indexType) const
	{
		//가능한 index type은 몇가지 없다
		assert(indexType == GL_UNSIGNED_BYTE || indexType == GL_UNSIGNED_SHORT || indexType == GL_UNSIGNED_INT && "Not valid index type");

		//이전에 buffer사용을 했을지도 모르니까 놓기
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		GLuint positionSlot = getAttribLocation(SHADER_POSITION);
		GLuint colorSlot = getAttribLocation(SHADER_SOURCE_COLOR);

		glEnableVertexAttribArray(positionSlot);
		glEnableVertexAttribArray(colorSlot);

		GLsizei stride = sizeof(Vertex2);
		const GLvoid *pCoords = &vertices[0].pos[0];
		const GLvoid *pColors = &vertices[0].color[0];

		glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
		glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);

		glDrawElements(drawMode, indexCount, indexType, indices);

		glDisableVertexAttribArray(positionSlot);
		glDisableVertexAttribArray(colorSlot);
	}

	void ShaderProgram::applyVBO2(const VertexBufferObject &vertexbuffer, const IndexBufferObject &indexbuffer)
	{
		//vbo
		GLuint positionSlot = getAttribLocation(SHADER_POSITION);
		GLuint colorSlot = getAttribLocation(SHADER_SOURCE_COLOR);

		//vertex buffer object 사용
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexbuffer.getBuffer());
		glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer.getBuffer());

		glEnableVertexAttribArray(positionSlot);
		glEnableVertexAttribArray(colorSlot);

		//TODO offset은 어떻게 분리하는게 좋을까?
		const GLvoid *colorOffset = (GLvoid*)(sizeof(float)*2);

		glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, vertexbuffer.getStride(), 0);
		glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, vertexbuffer.getStride(), colorOffset);

		glDrawElements(indexbuffer.getDrawMode(), indexbuffer.getCount(), indexbuffer.getIndexType(), 0);

		glDisableVertexAttribArray(positionSlot);
		glDisableVertexAttribArray(colorSlot);
	}
	*/

	void ShaderProgram::bindAttribLocation(GLuint index, const char* name) const 
	{
		glBindAttribLocation(program_, index, name); 
	}

	GLuint ShaderProgram::getAttribLocation(const char* name) const
	{
		GLint result= glGetAttribLocation(program_, name); 
		sora::gl::checkGlError("glGetAttribLocation");
		return result;
	}
	GLuint ShaderProgram::getUniformLocation(const char* name) const 
	{
		GLint result = glGetUniformLocation(program_, name); 
		sora::gl::checkGlError("glGetUniformLocation");
		return result;
	}
	std::vector<ShaderLocation> ShaderProgram::uniformLocationList() const
	{
		vector<ShaderLocation> list;

		//use는 어차피 실행되는 메소드니까 임시로 여기에 좀 끼어넣어서 쉐이더 테스트 해보자
		GLint maxUniformLen;
		GLint numUniform;
		
		glGetProgramiv(program_, GL_ACTIVE_UNIFORMS, &numUniform);
		glGetProgramiv(program_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);
		char *uniformName = (char*)malloc(sizeof(char) * maxUniformLen);
		for(int i = 0 ; i < numUniform ; i++)
		{
			GLint size;
			GLenum type;
			//get uniform information
			glGetActiveUniform(program_, i, maxUniformLen, NULL, &size, &type, uniformName);
			GLint location = glGetUniformLocation(program_, uniformName);

			ShaderLocation sl(uniformName, location, size, type);
			list.push_back(sl);
		}
		free(uniformName);

		return list;
	}

	std::vector<ShaderLocation> ShaderProgram::attributeLocationList() const
	{
		vector<ShaderLocation> list;

		GLint maxAttributeLen;
		GLint numAttribute;		
		glGetProgramiv(program_, GL_ACTIVE_ATTRIBUTES, &numAttribute);
		glGetProgramiv(program_, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxAttributeLen);

		char *attributeName = (char*)malloc(sizeof(char) * maxAttributeLen);
		for(int i = 0 ; i < numAttribute ; i++)
		{
			GLint size;
			GLenum type;
			glGetActiveAttrib(program_, i, maxAttributeLen, NULL, &size, &type, attributeName);
			GLint location = glGetAttribLocation(program_, attributeName);

			ShaderLocation sl(attributeName, location, size, type);
			list.push_back(sl);
		}
		free(attributeName);
		return list;

	}

	ShaderLocation::ShaderLocation(const char *name, GLint location, GLint size, GLenum type)
		: name_(name), location_(location), size_(size), type_(type)
	{
	}
	const std::string &ShaderLocation::name() const { return name_; }
	GLint ShaderLocation::location() const { return location_;}
	GLint ShaderLocation::size() const { return size_; }
	GLenum ShaderLocation::type() const { return type_; }

	std::string ShaderLocation::str() const
	{
		std::ostringstream oss;
		oss << "[Uniform]" << location() << "/" << name();
		return oss.str();
	}

	void ShaderProgramHelper::printShaderProgramLocation(const ShaderProgram &prog)
	{
		//shader 정보 출력
		vector<ShaderLocation> uniformLocationList = prog.uniformLocationList();
		for(std::size_t i = 0 ; i < uniformLocationList.size() ; i++)
		{
			const ShaderLocation &location = uniformLocationList[i];
			std::string str = location.str();
			LOGI("Uniform : %s\n", str.c_str());
		}
		vector<ShaderLocation> attributeLocationList = prog.attributeLocationList();
		for(std::size_t i = 0 ; i < attributeLocationList.size() ; i++)
		{
			const ShaderLocation &location = attributeLocationList[i];
			std::string str = location.str();
			LOGI("Attribute : %s\n", str.c_str());
		}
	}
}
#endif